/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */

/**
 * Software License, Version 1.0
 * 
 * Copyright 2004 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
/*
 This file is a clone of GridFTPClient.java form globus with some modification 
 to support GridFTP with NB.

 Original license statement is following:
 This file is licensed under the terms of the Globus Toolkit Public
 License, found at http://www.globus.org/toolkit/download/license.html.
 */
package cgl.narada.gridapps.nbgridftp.gridftpext;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.UnknownHostException;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.globus.ftp.DataChannelAuthentication;
import org.globus.ftp.DataSink;
import org.globus.ftp.DataSource;
import org.globus.ftp.FTPClient;
import org.globus.ftp.FeatureList;
import org.globus.ftp.FileRandomIO;
import org.globus.ftp.GridFTPSession;
import org.globus.ftp.HostPort;
import org.globus.ftp.HostPortList;
import org.globus.ftp.MarkerListener;
import org.globus.ftp.Session;
import org.globus.ftp.exception.ClientException;
import org.globus.ftp.exception.FTPReplyParseException;
import org.globus.ftp.exception.ServerException;
import org.globus.ftp.exception.UnexpectedReplyCodeException;
import org.globus.ftp.extended.GridFTPServerFacade;
import org.globus.ftp.vanilla.Command;
import org.globus.ftp.vanilla.Reply;
import org.globus.ftp.vanilla.TransferState;
import org.globus.gsi.gssapi.auth.Authorization;
import org.ietf.jgss.GSSCredential;

/**
   @author Sang Boem Lim
   $Date$
   $Revision$
 */

public class NBGridFTPClient extends FTPClient{

    private static Logger logger = 
	Logger.getLogger(NBGridFTPClient.class.getName());

    //utility alias to session and localServer
    protected GridFTPSession gSession;
    protected GridFTPServerFacade gLocalServer;

    /**
     * Constructs client and connects it to the remote server.
     * @param host remote server host
     * @param port remote server port
     */
    public NBGridFTPClient(String host, int port) throws IOException, 
        ServerException {
        
        gLocalServer = new NBGridFTPServerFacade();
        localServer = gLocalServer;
        gLocalServer.authorize();
        
        gSession = new GridFTPSession();
        session = gSession;
        
        controlChannel = new NBGridFTPControlChannel(host, port);
        controlChannel.open();
    }

    /**
     * Performs authentication with specified user credentials.
     *
     * @param credential user credentials to use.
     * @throws IOException on i/o error
     * @throws ServerException on server refusal or faulty server behavior
     */
    public void authenticate( GSSCredential credential) throws IOException, 
        ServerException{

        // By setting 'setAuthorization(null)' the client will not 
        // perform HostAuthorization check.
        ((NBGridFTPControlChannel)controlChannel).setAuthorization(null);

        ((NBGridFTPControlChannel)controlChannel).authenticate(credential);
        gLocalServer.setCredential(credential);
        gSession.authorized = true;
    }

    /**
     Performs remote directory listing, like FTPClient.list(). 
     <b>Note:</b> This method cannot be used
     in conjunction with parallelism or striping; set parallelism to
     1 before calling it. Otherwise, use the parametrized list().
     Unlike in vanilla FTP, here IMAGE mode is allowed. 
     For more documentation, look at FTPClient.
     */
    public Vector list() throws ServerException, ClientException, IOException {

        if (gSession.parallel > 1) {
            throw new ClientException(ClientException.BAD_MODE, 
                                      "list() cannot be called with " +
                                      "parallelism");
        }

        return super.list();
    }

    /**
     Use this method to list remote directory while in parallel transfer
     mode. The DataSink you support must be thread safe.
     Look at FTPClient for more documentation.
     **/
    public void list(String filter, String modifier, DataSink sink) 
        throws  ServerException, ClientException, IOException {

        if (!gSession.needsGridFTP()) {

            super.list(filter, modifier, sink);
            return;
        } 

        // all parameters set correctly (or still unset)?
        gSession.matches(localServer.getSession());

        // if transfer modes not defined,
        // set remote server (dest) as passive
        if (gSession.serverMode == gSession.SERVER_DEFAULT) {

            HostPort hp = setLocalPassive();
            this.setActive(hp);
        }

        super.actualList(filter, modifier, sink);
    }

    protected void listCheck() throws ClientException {
        // do nothing
    }

    /**
     Sets transfer mode.
     @param mode should be Session.MODE_STREAM, MODE_EBLOCK, or MODE_BLOCK
     **/
    public void setMode(int mode) throws IOException, ServerException { 

        String modeStr = null;
        switch (mode) {
            case Session.MODE_STREAM: modeStr = "S"; 
                break;
            case Session.MODE_BLOCK: modeStr = "B";
                break;
            case GridFTPSession.MODE_EBLOCK: modeStr = "E";
                break;
            default: throw new IllegalArgumentException("Bad mode"); 
        }
	
        actualSetMode(mode, modeStr);
    }

    /**
     Sets remote server TCP buffer size, in the following way:
     First see if server supports "SBUF" and if so, use it.
     If not, try the following commands until success:
     "SITE RETRBUFSIZE", "SITE RBUFSZ", "SITE RBUFSIZ", 
     "SITE STORBUFIZE",  "SITE SBUFSZ",  "SITE SBUFSIZ",
     "SITE BUFSIZE".
     Returns normally if the server confirms successfull setting of the remote
     buffer size, both for sending and for receiving data.
     Otherwise, throws ServerException.
     **/
    public void setTCPBufferSize(int size)throws IOException, ServerException {

        try {
            if (size <=0 ) {
                throw new IllegalArgumentException("size <= 0");
            }
	    
            boolean succeeded = false;
            String sizeString = Integer.toString(size);
            FeatureList feat = getFeatureList();

            if (feat.contains(FeatureList.SBUF)) {
                succeeded = tryExecutingCommand( new Command("SBUF", 
                                                             sizeString));
            }
	    
            if (!succeeded) {
                succeeded = tryExecutingCommand( new Command("SITE BUFSIZE", 
                                                             sizeString));
            }
	    
            if (!succeeded) {
                succeeded = tryExecutingTwoCommands(new Command("SITE " +
                                                                "RETRBUFSIZE", 
                                                                sizeString),
                                                    new Command("SITE "+ 
                                                                "STORBUFIZE", 
                                                                sizeString));
            }
	    
            if (!succeeded) {
                succeeded = tryExecutingTwoCommands(new Command("SITE RBUFSZ", 
                                                                sizeString),
                                                    new Command("SITE SBUFSZ", 
                                                                sizeString));
            }
	    
            if (!succeeded) {
                succeeded = tryExecutingTwoCommands(new Command("SITE RBUFSIZ",
                                                                sizeString),
                                                    new Command("SITE SBUFSIZ",
                                                                sizeString));
            }
	    
            if (succeeded) {
                this.gSession.TCPBufferSize = size;
            } else {
                throw new ServerException(ServerException.SERVER_REFUSED,
                                          "Server refused setting TCP " +
                                          "buffer size with any of the " +
                                          "known commands.");
            }

        } catch (FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
        }
    }

    private boolean tryExecutingTwoCommands(Command cmd1, Command cmd2) 
        throws IOException, FTPReplyParseException {

        boolean result = tryExecutingCommand(cmd1);
        if (result) {
            result = tryExecutingCommand(cmd2);
        }

        return result;
    }

    /*
     this is like controlChannel.executeCommand, only that negative reply it 
     returns "false" rather than throwing exception
     */
    private boolean tryExecutingCommand(Command cmd) throws IOException, 
        FTPReplyParseException {

        Reply reply = controlChannel.exchange(cmd);
        return Reply.isPositiveCompletion(reply);
    }
	
		
    /**
     Sets local TCP buffer size (for both receiving and sending).
     This method needs to be called BEFORE the local socket(s) get created.
     In other words, call it before calling setActive(), setPassive(), 
     get(), put(), etc.
     **/
    public void setLocalTCPBufferSize(int size) {

        if (size <=0 ) {
            throw new IllegalArgumentException("size <= 0");
        }

        gLocalServer.setTCPBufferSize(size);
    }
    
    /**
     Sets remote server to striped passive server mode (SPAS).
     **/
    public HostPortList setStripedPassive()throws IOException, ServerException{

	    Reply reply = null;

	    try {
            reply = controlChannel.execute(Command.SPAS);
	    } catch (UnexpectedReplyCodeException urce) {
            throw ServerException.embedUnexpectedReplyCodeException(urce);
	    } catch(FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
	    }

	    this.gSession.serverMode = gSession.SERVER_EPAS;
	    gSession.serverAddressList = new HostPortList(reply.getMessage());
	    return gSession.serverAddressList;
    }

    /**
     Sets remote server to striped active server mode
     (SPOR).
     **/
    public void setStripedActive(HostPortList hpl) throws IOException, 
        ServerException {

        Command cmd = new Command("SPOR", hpl.toFtpCmdArgument());

        try {
            controlChannel.execute(cmd);
        } catch (UnexpectedReplyCodeException urce) {
            throw ServerException.embedUnexpectedReplyCodeException(urce);
        } catch(FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
        }
	
        this.gSession.serverMode = gSession.SERVER_EACT;
    };
    
    /** Starts local server in striped passive mode. Since the local server
     is not distributed, it will only listen on one socket.
     @param port required server port; can be set to FTPServerFacade.ANY_PORT
     @param queue max size of queue of awaiting new data channel connection
     requests
     @return the HostPortList of 1 element representing the socket where the local
     server is listening
     **/
    public HostPortList setLocalStripedPassive(int port, int queue) 
        throws IOException{

        return gLocalServer.setStripedPassive(port, queue);
    }

    /**
     Behaves like 
     setLocalStripedPassive(FTPServerFacade.ANY_PORT, FTPServerFacade.DEFAULT_QUEUE)

     **/
    public HostPortList setLocalStripedPassive() throws IOException{

        return gLocalServer.setStripedPassive();
    }

    /** Starts local server in striped active mode.
     setStripedPassive() must be called before that.
     This method takes no parameters. HostPortList of the remote
     server, known from the last call of setStripedPassive(), is stored
     internally and the local server will connect to this address.
     **/
    public void setLocalStripedActive() throws ClientException, IOException{

        if (gSession.serverAddressList == null) {
            throw new ClientException(ClientException.CALL_PASSIVE_FIRST);
        }

        try {
            gLocalServer.setStripedActive(gSession.serverAddressList);
        } catch (UnknownHostException e) {
            throw new ClientException(ClientException.UNKNOWN_HOST);
        }
    }

    /** Behaves almost like FTPClient.get(). 
     Additionally, throws ClientException if Mode E is needed.
     **/
    public void  get(String remoteFileName, DataSink sink,
                     MarkerListener mListener) throws IOException,
        ClientException, ServerException {

        if (!gSession.needsGridFTP()) {
            super.get(remoteFileName, sink, mListener);
            return;
        } 
	
        throw new ClientException(ClientException.BAD_MODE,
                                  "This method does not implement mode E.");
    }


    /**
     Retrieves file from remote server using extended block mode.
     @param remoteFileName file to retrieve
     @param size number of bytes of remote file to transmit
     @param sink data sink to store the file
     @param mListener marker listener
     **/
    public void extendedGet(String remoteFileName, long size,
                            DataSink sink,         MarkerListener mListener)
        throws IOException, ClientException, ServerException {
	
        // servers support GridFTP?
        checkNBGridFTPSupport();
        // all parameters set correctly (or still unset)?
        gSession.matches(localServer.getSession());
	
        //mode E
        if (gSession.transferMode != GridFTPSession.MODE_EBLOCK) {
            throw new ClientException(ClientException.BAD_MODE,
                                      "Extended transfer mode is necessary");
        }

        // if transfer modes not defined,
        // set remote server (source) as active
        if (gSession.serverMode == gSession.SERVER_DEFAULT) {
            HostPort hp = setLocalPassive();
            this.setActive(hp);
        }

        logger.debug("localServer.store()");
        gLocalServer.store(sink);

        controlChannel.write(new Command("ERET", 
                                         "P 0 " + size + " " +remoteFileName));

        transferRun( localServer.getControlChannel(), mListener);
    }

    /** Behaves almost like FTPClient.put(). 
     Additionally, throws exception if Mode E is needed.
     **/
    public void put(String remoteFileName, DataSource source,
                    MarkerListener mListener)
        throws IOException, ServerException, ClientException{

        
        if (!gSession.needsGridFTP()) {
            super.put(remoteFileName, source, mListener);
            return;
        } 
        
        throw new ClientException(ClientException.BAD_MODE,
                                  "This method does not implement mode E.");
    }

    /**
     Stores file at remote server using extended block mode.
     @param remoteFileName file name to store
     @param source source for the data to transfer
     @param mListener marker listener
     **/
    public void extendedPut(String remoteFileName, DataSource source,
                            MarkerListener mListener)
        throws IOException, ServerException, ClientException{

        // servers support GridFTP?
        checkNBGridFTPSupport();
        // all parameters set correctly (or still unset)?
        gSession.matches(localServer.getSession());
	
        //mode E
        if (gSession.transferMode != GridFTPSession.MODE_EBLOCK) {
            throw new ClientException(ClientException.BAD_MODE,
                                      "Extended transfer mode is necessary");
        }

        // if transfer modes have not been defined, 
        // set this (source) as active
        if (gSession.serverMode == gSession.SERVER_DEFAULT) {
            HostPort hp = this.setPassive();
            setLocalActive();
        }

        logger.debug("localServer.retrieve()");
        localServer.retrieve(source);

        controlChannel.write(new Command("ESTO",
                                         "A 0 " + remoteFileName));
	
        transferRun(localServer.getControlChannel(),
                    mListener);			     
    }

    /*
     3rd party transfer code
     */


    /**
     * Performs a third-party transfer between two servers using extended block mode.
     * If server modes are unset, source will be set to active
     * and destination to passive.
     *
     * @param remoteSrcFile source filename
     * @param destination     destination server
     * @param remoteDstFile destination filename
     * @param mListener      transer progress listener.
     *                      Can be set to null.
     */
    public void extendedTransfer(String remoteSrcFile, 
                                 NBGridFTPClient destination, 
                                 String remoteDstFile,
                                 MarkerListener mListener)
        throws IOException, ServerException, ClientException {

        // servers support GridFTP?
        checkNBGridFTPSupport();
        destination.checkNBGridFTPSupport();
        // all parameters set correctly (or still unset)?
        gSession.matches(destination.gSession);
	
        //mode E
        if (gSession.transferMode != GridFTPSession.MODE_EBLOCK) {
            throw new ClientException(ClientException.BAD_MODE,
                                      "Extended transfer mode is necessary");
        }

        // if transfer modes have not been defined, 
        // set this (source) as active
        if (gSession.serverMode == gSession.SERVER_DEFAULT) {
	    
            HostPort hp = destination.setPassive();
            this.setActive(hp);
	    
        } 

        long sourceFileSize = getSize(remoteSrcFile);

        destination.controlChannel.write( new Command("ESTO",
                                                      "A 0 " + remoteDstFile));

        controlChannel.write(new Command("ERET", "P 0 " + sourceFileSize +
                                         " " + remoteSrcFile));

        transferRun(destination.controlChannel, mListener);
    }

    /**
     assure that the server supports extended transfer features;
     throw exception if not
     **/
    protected void checkNBGridFTPSupport() 
        throws IOException, ServerException{	    

        FeatureList fl = getFeatureList();
        if (
            !(fl.contains(FeatureList.PARALLEL)
              && fl.contains(FeatureList.ESTO)
              && fl.contains(FeatureList.ERET)
              && fl.contains(FeatureList.SIZE))
            ) {
            throw new ServerException(ServerException.UNSUPPORTED_FEATURE);
        }
        logger.debug("okay, server supports extended, parallel and striped");
    }

    /*
     end 3rd party transfer code
     */

    /**
     Sets data channel authentication mode (DCAU)
     @param type for 2-party transfer must be
     DataChannelAuthentication.SELF or DataChannelAuthentication.NONE
     **/
    public void setDataChannelAuthentication(DataChannelAuthentication type) 
        throws IOException, ServerException{

        Command cmd = new Command("DCAU", type.toFtpCmdArgument());
        try{

            controlChannel.execute(cmd);

        } catch (UnexpectedReplyCodeException urce) {
            throw ServerException.embedUnexpectedReplyCodeException(urce);
        } catch(FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
        }

        this.gSession.dataChannelAuthentication = type;

        gLocalServer.setDataChannelAuthentication(type);
    }
    
    /**
     Sets compatibility mode with old GSIFTP server.
     Locally sets data channel authentication to NONE 
     but does not send the command
     to the remote server (the server wouldn't understand it)
     **/
    public void setLocalNoDataChannelAuthentication() {
        gLocalServer.setDataChannelAuthentication(DataChannelAuthentication.NONE);
    }

    /**
     Returns data channel authentication mode (DCAU).
     @return data channel authentication mode
     **/
    public DataChannelAuthentication getDataChannelAuthentication() {
        return gSession.dataChannelAuthentication;
    }

    /**
     Sets data channel protection level (PROT).
     **/
    public void setDataChannelProtection(int protection)
        throws IOException,
        ServerException {

        String protectionStr = null;
        switch(protection) {
        case GridFTPSession.PROTECTION_CLEAR:
            protectionStr = "C"; break;
        case GridFTPSession.PROTECTION_SAFE:
            protectionStr = "S"; break;
        case GridFTPSession.PROTECTION_CONFIDENTIAL:
            protectionStr = "E"; break;
        case GridFTPSession.PROTECTION_PRIVATE:
            protectionStr = "P"; break;
        default: throw new IllegalArgumentException("Bad protection: " +
                                                    protection);
        }
	
        Command cmd = new Command("PROT", protectionStr);
        try {

            controlChannel.execute(cmd);

        } catch (UnexpectedReplyCodeException urce) {
            throw ServerException.embedUnexpectedReplyCodeException(urce);
        } catch(FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
        }

        this.gSession.dataChannelProtection = protection;

        gLocalServer.setDataChannelProtection(protection);	
    }
    
    /**
     Returns data channel protection level.
     @return data channel protection level
     **/
    public int getDataChannelProtection() {
        return gSession.dataChannelProtection;
    }

    /**
     * Sets authorization method for the control channel.
     *
     * @param authorization authorization method.
     */
    public void setAuthorization(Authorization authorization) {
        ((NBGridFTPControlChannel)this.controlChannel).setAuthorization(authorization);
    }
    
    /**
     * Returns authorization method for the control channel.
     * 
     * @return authorization method performed on the control channel.
     */
    public Authorization getAuthorization() {
        return ((NBGridFTPControlChannel)this.controlChannel).getAuthorization();
    }



    // basic compatibility API

    public void get(String remoteFileName,
                    File localFile) 
        throws IOException,
        ClientException,
        ServerException {
        if (gSession.transferMode == GridFTPSession.MODE_EBLOCK) {
            DataSink sink = 
                new FileRandomIO(new RandomAccessFile(localFile, "rw"));
            get(remoteFileName, sink, null);
        } else {
            super.get(remoteFileName, localFile);
        }
    }

    public void put(File localFile,
                    String remoteFileName,
                    boolean append) 
        throws IOException,
        ServerException,
        ClientException{
        if (gSession.transferMode == GridFTPSession.MODE_EBLOCK) {
            DataSource source = 
                new FileRandomIO(new RandomAccessFile(localFile, "r"));
            put(remoteFileName, source, null, append);
        } else {
            super.put(localFile, remoteFileName, append);
        }
    }

    protected TransferState actualPut(String remoteFileName,
                                      DataSource source,
                                      MarkerListener mListener,
                                      boolean append)
        throws IOException, ServerException, ClientException{

        checkTransferParams();
        
        localServer.retrieve(source);
        
        if (append) {
            controlChannel.write(new Command("APPE", remoteFileName));
        } else {
            ((NBGridFTPControlChannel)controlChannel).
                write2Proxy("STOR::" + remoteFileName, false);
            controlChannel.write(new Command("STOR", remoteFileName));
        }
        
        return transferStart(localServer.getControlChannel(),
			     mListener);			     
    }
    
    /**
     * Sets remote server active, telling it to connect to the given
     * address.
     * @param hostPort the address to which the server should connect
     */
    public void setActive(HostPort hostPort) throws IOException, 
        ServerException {

        ((NBGridFTPControlChannel)controlChannel).
            write2Proxy(hostPort.getHost() + ":" + hostPort.getPort(), false);

        hostPort = ((NBGridFTPControlChannel)controlChannel).readFromProxy();

        Command cmd = new Command("PORT", hostPort.toFtpCmdArgument());

        try {
            controlChannel.execute(cmd);
        } catch (UnexpectedReplyCodeException urce) {
            throw ServerException.embedUnexpectedReplyCodeException(urce);
        } catch(FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
        }
        
        this.session.serverMode = session.SERVER_ACTIVE;
    }

    /**
       Sets remote server active, telling it to connect to the client.
       setLocalPassive() must be called beforehand.
     **/
    public void setActive() throws IOException, ServerException,
        ClientException {
        
        Session local = localServer.getSession();
        if (local.serverAddress == null) {
            throw new ClientException(ClientException.CALL_PASSIVE_FIRST);
        }
        setActive(local.serverAddress);
    }

        /** Closes connection. Sends QUIT and closes connection 
     * 	even if the server reply was not positive. Also, closes
     *  the local server.
     **/
    public void close() throws IOException, ServerException {

        try {
            Reply reply = controlChannel.execute(Command.QUIT);
        } catch (FTPReplyParseException rpe) {
            throw ServerException.embedFTPReplyParseException(rpe);
        } catch (UnexpectedReplyCodeException urce) {
            throw ServerException.
                embedUnexpectedReplyCodeException(urce,
                                                  "Server refused closing");
        } finally {

            ((NBGridFTPControlChannel)controlChannel).
                write2Proxy("", true);
            try {
                controlChannel.close();
            } finally {
                localServer.close();
            }
            
        }
    }
}

